iT邦幫忙

2025 iThome 鐵人賽

DAY 29
0
生成式 AI

阿,又是一個RAG系列 第 29

Day28: 清理檢索回來的文檔

  • 分享至 

  • xImage
  •  

Intro

我們今天將會搭建我們開篇以來最複雜的 workflow,他的長相如下:
https://ithelp.ithome.com.tw/upload/images/20251013/20177855R9yV7xxYaq.jpg
但實測後這個 workflow 的答題率仍然只有 7/10
究竟是怎麼回事,我們接著看吧

Workflow 的 Design

  • 要看懂這個 workflow 可以從中心有分叉的 retrieval_evaluator 藍色長方形看起
    • 藍色長方形,放的是處理邏輯,在 workflow 裡,這叫 step
    • 圓形,放的是資料,在 workflow 裡,這叫 event
  • 我們從 retrieval evaluator 往 QueryTransformEvent 的分支看起:
  • 它會經過以下 step:
    • transform_query: 重新把 query 轉換成適合搜尋的關鍵字
    • tavily_search: 把新產生的 query 使用 tavily 進行檢索
    • retrieval_filter: 因為 tavily 檢索回來的結果比較雜,我們這邊 prompt llm 對查回來的結果進行一次 filter
      • 原本的預期是加了這串之後可以把上回的問題解決,但從結果來看並沒有
        • 上回的問題:因為 context 的資訊雜訊太多而導致 llm 在詳解明確提到答案,但選擇題還是選錯
    • merge_context:
      • 這邊會根據一開始 retrieval evaluator 的判斷 (correct, incorrect, ambiguous) 來決定是要拼接上一輪檢索回來的 old_context, 還是直接捨棄用新的

釐清了核心之後,就剩下

  • 最上層的 query_result 分支,這邊就是普通的基於 context 來回答 query
  • 從 Start Event 開始的 query2keyword -> wiki_search -> retrieval_evaluator

Key step details

由於這邊內容比較多,我們捨棄了逐程式碼講解,轉而只討論關鍵步,完整實作可以在這裡找到
首先是 retrieval evaluator,這個和我們在 day26 做的事情一樣,我們只是把它重新包成 workflow 的一個 step

retrieval_evaluator

@step
async def retrieval_evaluator(self, ctx: Context, ev: RetrieveEvent) -> QueryEvent | QueryTransformEvent:
    retrieved_nodes = ev.retrieved_nodes
    context = make_context(retrieved_nodes)
    query_str = await ctx.store.get("qset")
    llm = self.llm
    prompt = RETRIEVAL_EVALUATOR_PROMPT.format(query=query_str, context=context)
    response = json.loads(llm.complete(prompt).text)
    verdict = response['verdict']
    feedback = response['feedback']
    if verdict == 'correct':
        return QueryEvent(query=query_str, context=context)
    else:
        return QueryTransformEvent(query=query_str, feedback=feedback)
  • 我們在 prompt 裡要求 llm 回傳兩個 key:
    • verdict: 有 correct, incorrect 和 ambiguous,用來判斷 wiki search 的結果是否要保留
    • feedback: 這邊會指出說現在版本的 context 會有什麼問題,我們會把這個值丟給下一個 step: transform_query 用來協助產生下一次檢索的 query
      • 範例回傳: 'context 只詳述合谷穴的定位與功能,未提及「四關穴」或其與太衝等穴位的組合,缺少關鍵組成資訊。建議檢索「四關穴 組合/合谷 太衝」或查太衝穴資料以確認答案。'
  • 而如果 evaluator 判斷目前的 context 已經足夠完整回答問題(verdict 的值會是 correct),就會直接回傳 QueryEvent 接最後的 query_result

transform_query

  • 這邊一樣是 prompt llm 要求產生適合檢索 tavily 的關鍵字,但我們加上了前述 evaluator 評論的 feedback
@step
async def transform_query(self, ctx: Context, ev: QueryTransformEvent) -> TavilyEvent:
    query = ev.query
    feedback = ev.feedback
    llm = self.llm
    prompt = QUERY_TRANSFORM_PROMPT.format(query=query, feedback=feedback)
    response = json.loads(llm.complete(prompt).text)
    num_subqueries = len(response['refined_queries'])
    await ctx.store.set("num_subqueries", num_subqueries)
    return TavilyEvent(tavily_query=response['refined_queries'])
  • 順帶一題,workflow 可以逐步的呼叫 step,用來協助我們在開發的時候 debug
  • 範例呼叫如下:
wf = CorrectiveRAGWorkflow(llm=mini, wiki_searcher=wiki_searcher)
refined_query = await wf.transform_query(ctx, evaluate_result)
refined_query
  • 這邊是結果:
TavilyEvent(tavily_query=['四關穴 組成', '四關穴 包含哪些穴位', '合谷 太衝 是否構成 四關穴'])

retrieval_filter

  • 由於 tavily 檢索回來無論是數量,還是內容,相對於 wiki 來說都比較亂,因此我們這邊是逐個 node 的 prompt llm 進行篩選
  • 這邊顯然會有速度與精度的權衡問題,這邊其他的選擇是:
    • 改為一次 input 多個 node: 像我們day10: citationQueryEngine做的一樣
    • 改為使用類似 bert-like 的 model: 像我們Day9: longllmlingua2做的一樣
    • 我們這邊的考量是,我們希望保存這些 reference_context 當成是我們最後的 dataset,因此使用了成本最高的方式,希望先得到夠好的結果,我們再來考慮省錢
@step
async def retrieval_filter(self, ctx: Context, ev: TavilyRetrieveEvent) -> ContextMergeEvent:
    retrieved_result = ev.retrieved_nodes
    llm = self.llm
    rvs = []
    for idx, item in enumerate(retrieved_result):
        print(f"{idx}", end=', ')
        sub_query, doc = item
        text = doc.text
        prompt = DOCUMENT_EXTRACT_PROMPT.format(query=sub_query, document=text)
        response = json.loads(llm.complete(prompt).text)
        rvs.append((response, sub_query, doc))
    return ContextMergeEvent(retrieved_nodes=rvs)
  • 這是我們的 prompt
DOCUMENT_EXTRACT_PROMPT = PromptTemplate(
    template="""你是精準的文本抽取器。你的任務有兩個:
1. 判定下面的 DOCUMENT 是否應保留作為 QUERY 的候選 context。
2. 如果保留,抽取最能直接回答 QUERY 的句子(1-3 句)。

條件:
- 只摘自原文(不要改寫或新增資訊)。
- 優先保留直接回答 QUERY 或含關鍵詞的句子。
- 若 DOCUMENT 與 QUERY 明顯無關、是廣告、重複或空白,回 keep=false。
- 嚴格輸出 JSON 格式,不要多餘文字。

輸出 JSON schema:
{
  "keep": true|false,                  # 是否保留 DOCUMENT
  "reason": "一句話說明為何保留或捨棄",
  "important_spans": [                  # 若保留,列出 0~3 個最關鍵短句
    "片段內容1", "片段內容2", "片段內容3"
  ]
}

輸入:
QUERY: {query}
DOCUMENT: {document}
"""
)

  • 完整程式碼可以在: notebook 找到

Results:

我們在 10 題的測試裡,gemma 從 答對 7 題 持平仍然為 答對 7 題
這是我們的 context relevancy vs Faithfulness
https://ithelp.ithome.com.tw/upload/images/20251013/20177855T9EP5H7pxG.jpg
從圖上我們可以看到,有不少點的 context relevancy 分數掉到 0.8 以下,這個 0.8 的占比包含:

  • 有 0.5 分是檢索回來的主題是否與 query 相關
  • 有 0.5 分是是否可以依靠這個 context 完整回答這題問題
  • 因此我們還是可以看出有一半的題目在檢索上仍然有問題,儘管我們已經檢索了兩次,只是這次從沒有檢索到相關的資料,已經逐步演變為,沒有檢索到可以完整回答這個問題的資料(沒找到 -> 沒找全)

關於 Faithfulness 掉到 0 的題目:

  • 兩題都是屬於模型為了回答問題而自行產生了不存在於 context 的內容

    • 我們第一次感覺到 Fithfulness 有做事
  • 這部分我覺得是因為我們的 context 已經變得比較精簡了,所以使得我們的 Faithfulness 看起來更 work

最後是我們以 問題的 index 為 x 軸的答題情況:
https://ithelp.ithome.com.tw/upload/images/20251013/20177855TBwd4qTwtC.jpg
我們這次看到,答錯的題目主要都伴隨了 context relevancy 沒有滿分

  • 有兩題是以: 共有幾個? 或者下列敘述共幾項正確這種結尾的題目。
    • 這明確指示出只檢索兩輪這種問題通常答不對的
  • 還有一題是: 下列何穴主心下滿?,而參考資料知道:井穴主心下滿,但是選項沒有井穴,因此同樣是這種需要多輪查詢的問題
  • 也就是說:目前系統的主要問題又回到檢索區塊,因為我們強迫只可以檢索兩輪,導致實際上需要檢索多輪的題目仍然得不到足夠好的 context,我們明天來把這個問題解決吧

Summary

  • 我們今天的工作首先關注在把 workflow 構建起來,並且學習了單步呼叫 workflow 的實際方法
  • 接著我們實作了把檢索回來的 node 先逐一過濾並提取關鍵資訊的步驟,這間接提升了我們 Faithfulness 的準確率,至少我們現在把 Faithfulness 不高的題目抓出來,都可以明確的看到確實是憑空產生了沒根據的內容
  • 此外我們開發過程中發現: 直接使用 tavily 產生出 5 個 query,很多時候其實第一個 query 就已經解答問題了,導致我們的 context 又徒增了不必要的複雜性
  • 目前來說,系統的主要錯題集中於:
    • 有找到相關的資料,但是仍沒辦法直接回答這個問題,尤其是題型類似於 下列敘述共幾項正確 這種無法單靠兩次檢索就可以答對
  • 解決辦法是根據當前情況再來產生新的 tavily query,類似於我們Day15的ReAct Agent
    • 不過我們現在已經掌握了更多的其他技巧,我們明天來兜一個補強版的看能不能破台吧!

其他

  • 這邊清洗後的 context 已經屬於乾淨的 context,如果確實可以正確回答問題的話,可以留存下來成為我們新一輪的 dataset 替我們的系列文章形成完美的閉環

上一篇
Day27: 開源的標註工具: Label-Studio
下一篇
Day29: multi step workflow 與 Day30: 總結
系列文
阿,又是一個RAG30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言